/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.compiler.checker;


import dex.compiler.checker.type.BooleanType;
import dex.compiler.checker.type.ErrorType;
import dex.compiler.checker.type.IntegerType;
import dex.compiler.checker.type.Type;
import dex.compiler.model.base.Place;
import dex.compiler.model.expression.Infix;
import dex.compiler.problem.Reporter;
import static dex.compiler.checker.ExpressionProblems.*;


/**
 * The type checking rules for infix operators.
 */
class InfixRules {

	
	/** The reporter used to report errors. */
	private Reporter reporter;

	/** The place to include in error reports. */
	private Place place;
	
	/** The operator to check. */
	private Infix operator;
	
	/** The left operand type. */
	private Type left;
	
	/** The right operand type. */
	private Type right;
	
	
	/**
	 * Constructs a new <code>InfixRules</code>.
	 * 
	 * @param r  The reporter used to report errors
	 * @param p  The place to include in error reports
	 * @param op  The operator to check
	 * @param left  the left operand type to check
	 * @param right  the right operand type to check
	 */
	public InfixRules(Reporter r, Place p, Infix op, Type left, Type right) {
		this.reporter = r;
		this.place = p;
		this.operator = op;
		this.left = left;
		this.right = right;
	}


	/**
	 * Checks that the operator expression is valid.
	 * 
	 * @return  the type that the operator expression will evaluate to
	 */
	public Type check() {		
		if ((left == BooleanType.INSTANCE) || (right == BooleanType.INSTANCE)) {
			return checkBoolean();
		}
		
		if (left.isCompound() || right.isCompound()) {
			return resolveCompoundType();
		}
		
		// Only thing left is integers.
		// FIXME: Add support for floating-point
		
		return resolveIntegerType();
	}
	
	
	/**
	 * Invoked by check() if either the left or right operand is boolean.
	 *
	 * <p>If either parameter is boolean, then both of them must be, or the ErrorType
	 * is returned.  For instance, <code>3 && true</code> is illegal.
	 * 
	 * <p>If both are boolean but the operator doesn't work on boolean types, then
	 * ErrorType is returned.  For instance, <code>true &lt; false</code> is 
	 * illegal.
	 * 
	 * <p>Otherwise the boolean type is returned, since the operators that work on
	 * boolean arguments always return the boolean type.
	 *
	 * @return  the boolean type
	 */
	private Type checkBoolean() {
		if ((left == BooleanType.INSTANCE) && (right == BooleanType.INSTANCE)) {
			if (operator.isBoolean()) {
				return BooleanType.INSTANCE;
			} else {
				reporter.error(BAD_BOOLEAN_OPERATOR, place, operator.getSymbol());
				return ErrorType.INSTANCE;
			}
		} else {
			reporter.error(BAD_BOOLEAN_ARGUMENTS, place, operator.getSymbol());
			return ErrorType.INSTANCE;
		}
	}
	
	
	/**
	 * Invoked by resolveType when either left or right is a compound type.
	 * 
	 * <p>The only operators that work on compound types are == and !=.  If 
	 * the operator is anything else, then the error type is returned.
	 * 
	 * <p>Since one parameter is compound, both must be.  For instance,
	 * <code>3 == null</code> is illegal.  If either operand is 
	 * non-compound, then the error type is returned.
	 * 
	 * <p>Otherwise this method returns the boolean type, since both ==
	 * and != evaluate to boolean.
	 * 
	 * @return  the boolean type
	 */
	private Type resolveCompoundType() {
		if (left.isCompound() && right.isCompound()) {
			if (operator.isCompound()) {
				// Must be either == or !=
				return BooleanType.INSTANCE;
			} else {
				reporter.error(BAD_COMPOUND_OPERATOR, place, operator.getSymbol());
				return ErrorType.INSTANCE;
			}
		} else {
			reporter.error(BAD_COMPOUND_ARGUMENTS, place, operator.getSymbol());
			return ErrorType.INSTANCE;
		}
	}


	/**
	 * Invoked by resolveType when all else fails.
	 * 
	 * <p>Both the left and the right operands must be of integer types.  Otherwise
	 * the error type is returned.
	 * 
	 * <p>The operator must be applicable to integer operands, or the error type is 
	 * returned.
	 * 
	 * <p>Otherwise, the return type is an integer type.  The bit width of the integer
	 * type is the maximum of the bit widths of the operand types.  The returned
	 * type will be unsigned if either operand is unsigned.
	 * 
	 * @return  the integer type that results from applying the operator to those operands
	 */
	private Type resolveIntegerType() {
		if ((!(left instanceof IntegerType)) || (!(right instanceof IntegerType))) {
			// This is actually impossible given the rules of resolveType
			throw new IllegalStateException();
			/*
			reporter.error(BAD_INTEGER_ARGUMENTS, place, operator.getSymbol());
			return ErrorType.INSTANCE;
			*/
		}
		
		if (!operator.isInteger()) {
			reporter.error(BAD_INTEGER_OPERATOR, place, operator.getSymbol());
			return ErrorType.INSTANCE;
		}

		IntegerType leftInt = (IntegerType)left;
		IntegerType rightInt = (IntegerType)right;

		// Automatically widen and convert signed to unsigned
		int width = Math.max(leftInt.getWidth(), rightInt.getWidth());
		boolean sign = leftInt.isSigned() && rightInt.isSigned();
		
		return IntegerType.get(sign, width);
	}

	
}
